﻿using System;
using System.Reflection;

namespace WCSFContrib.Extensions.EventBroker
{
	/// <summary>
	/// Event broking utilities
	/// </summary>
	public static class EventUtils
	{
		/// <summary>
		/// Validate that the subscriber can subscribe to the published event. Possible signatures are:
		/// 
		/// Publishers:
		/// event EventHandler and event EventHandler<DataEventArgs<T>>
		/// 
		/// Subscribers:
		/// Subsrciber(), Subscriber(<T>), Subscriber(object, EventArgs) and Subscriber(object, DataEventArgs<T>)
		/// 
		/// or the publisher and subscriber EventArg types must be the same.
		/// </summary>
		/// <param name="publisher"></param>
		/// <param name="subscriber"></param>
		/// <returns>True if the publisher += subscriber assignment will succeed.</returns>
		public static bool ValidatePublicaterSubsciberPair(EventInfo publisher, MethodInfo subscriber)
		{
			ParameterInfo[] subscriberParameters = subscriber.GetParameters();
			if (subscriberParameters.Length == 0)
			{
				// Simple parameterless subscriber! Allways works.
				return true;
			}

			// Find if there is a generic DataEventArg on the publisher and if so find the type of the generic arg.
			// <EventName>(ojbect sender, <EventArgType> e);
			Type eventArgType = publisher.EventHandlerType.GetMethod("Invoke").GetParameters()[1].ParameterType;

			if (HasEventHandlerSignature(subscriber))
			{
				// We are looking at a subscribing method which has an event hanler style
				// (Object sender, EventArgs e) or 
				// (Object sender, GenericDataEventArgs<genericArgType-above> e) or
				// (Object sender, <EventArgsInheritor> e)

				// First parameter is not object -> This is an event handler and they always have object as first parameter
				// Second parameter is the same generic type as the publiser with the same generic arg
				return subscriberParameters[0].ParameterType.Equals(typeof(Object)) &&
				       subscriberParameters[1].ParameterType.Equals(eventArgType);
			}
			else if (subscriberParameters.Length > 0)
			{
				if (!publisher.EventHandlerType.IsGenericType)
					return false;

				Type publisherEventArgType = publisher.EventHandlerType.GetGenericArguments()[0];
				Type[] publisherGenericArguments = publisherEventArgType.GetGenericArguments();

				// The number of arguments must be the same
				if (publisherGenericArguments == null ||
					 publisherGenericArguments.Length != subscriberParameters.Length)
					return false;

				for (int i = 0; i < publisherGenericArguments.Length; i++)
				{
					if (!publisherGenericArguments[i].Equals(subscriberParameters[i].ParameterType))
						return false;
				}
				return true;
			}

			throw new NotSupportedException(string.Format("The case of publisher {0}.{1} and subscriber {2}.{3} is not handled!",
				 publisher.DeclaringType.FullName, publisher.Name,
				 subscriber.DeclaringType.FullName, subscriber.Name));
		}

		/// <summary>
		/// Validates the method signature to find methods with an eventhandling signature.
		/// </summary>
		/// <param name="subscriber">The method to inspect.</param>
		/// <returns>True if the method has a signature (ojbect, EventArgs).</returns>
		public static bool HasEventHandlerSignature(MethodInfo subscriber)
		{
			ParameterInfo[] subscriberParameters = subscriber.GetParameters();
			return subscriberParameters.Length == 2 &&
					 subscriberParameters[0].ParameterType.Equals(typeof(object)) &&
			       typeof(EventArgs).IsAssignableFrom(subscriberParameters[1].ParameterType);
		}
	}
}